/* Copyright 2008 Bas van den Berg
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ElevatorController.h"

#include "TaskQueue.h"
#include "Global.h"
#include "CmdVisitor.h"

#include "Area.h"
#include "Room.h"
#include "MudExit.h"
#include "MudPlayer.h"
#include "config.h"
#include "Error.h"
#include "myassert.h"
#include "Utils.h"

using namespace std;
using namespace Chronos;

namespace Chronos {

class ElevatorControllerTask : public Task {
public:
	ElevatorControllerTask(ElevatorController* controller_)
		: controller(controller_)
	{}
	virtual unsigned int run() {
		if (controller == 0) return 0;
		return controller->run();
	}
	void cancel() { controller = 0; }
private:
	ElevatorController* controller;
};

}


ElevatorController::ElevatorController(Room& elevator_, MudExit* toElevator_, Area& area_)
	: currentFloor(0)
	, state(IDLE)
	, elevatorEntry(toElevator_)
	, area(area_)
	, elevator(elevator_)
	, task(0)
{}


ElevatorController::~ElevatorController() {
	if (task) task->cancel();
	if (!elevatorEntry->getRoom()) delete elevatorEntry;
    for (FloorsConstIter iter = floors.begin(); iter != floors.end(); ++iter) {
		MudExit* from = iter->second->fromElevator;
		if (!from->getRoom()) delete from;
		delete iter->second;
	}
}


void ElevatorController::addFloor(int floorID, unsigned int roomID) {
	if (floors.empty()) currentFloor = floorID;
	floors[floorID] = new Floor(roomID, new MudExit("out", "The elevator exit", new RoomPortal(area, roomID, "", "", "")));

	elevator.addObject(new ElevatorButton(Utils::itoa(floorID), *this, floorID));
	area.getRoom(roomID).addObject(new ElevatorButton("button", *this, floorID));
}


unsigned int ElevatorController::run() {
	switch (state) {
	case IDLE:
		ASSERT_FAIL();
		return 0;
	case DOORS_OPEN: 
		closeDoors();
		return seconds(handleRequests());
	case MOVING:
		return moveElevator();
	}
	return 0;
}


void ElevatorController::request(MudPlayer& player, int floor) {
	FloorsConstIter iter = floors.find(floor);
	if (iter == floors.end()) {
		throw MudError("ElevatorController: unknown floor");
	}
	if (currentFloor == floor && state == IDLE) {
		openDoors();
		reschedule(ELEVATOR_OPEN_TIME);
		return;
	}
	if (currentFloor == floor && state == DOORS_OPEN) {
		player.write("The doors are already open.\n");
		task->cancel();
		reschedule(ELEVATOR_OPEN_TIME);
		return;
	}
	addRequest(floor);
}


void ElevatorController::addRequest(int floor) {
	RequestsIter iter = find(requests.begin(), requests.end(), floor);
	if (iter != requests.end()) return;

	requests.push_back(floor);
	if (state == IDLE) reschedule(handleRequests());
}


int ElevatorController::handleRequests() {
	if (requests.empty()) return 0;

	int nextFloor = requests.front();
	if (nextFloor < currentFloor) elevator.showText("The elevator starts moving down.");
	else elevator.showText("The elevator starts moving up.");
	state = MOVING;
	return ELEVATOR_MOVE_TIME;
}


void ElevatorController::reschedule(unsigned int delay_secs) {
	task = new ElevatorControllerTask(this);
	Global<TaskQueue>()->addTask(task, seconds(delay_secs));
}


unsigned int ElevatorController::moveElevator() {
	int destFloor = requests.front();
	if (destFloor > currentFloor) currentFloor++;
	else currentFloor--;
	if (currentFloor == destFloor) {
		requests.pop_front();
		elevator.showText("The elevator stops on floor " + (string)Utils::itoa(currentFloor) + ".");
		area.getRoom(floors[currentFloor]->roomID).showText("The elevator arrives.");
		openDoors();
		return seconds(ELEVATOR_OPEN_TIME);
	} else {
		elevator.showText("The elevator passes floor " + (string)Utils::itoa(currentFloor) + ".");
		return seconds(ELEVATOR_MOVE_TIME);
	}
}


void ElevatorController::openDoors() {
	Floor* floor = floors[currentFloor];
	Room* currentRoom = &area.getRoom(floor->roomID);
	currentRoom->addObject(elevatorEntry);
	elevator.addObject(floor->fromElevator);
	state = DOORS_OPEN;
	currentRoom->showText("The elevator doors open.");
	elevator.showText("The doors open.");
}


void ElevatorController::closeDoors() {
	Room* currentRoom = elevatorEntry->getRoom();
	currentRoom->removeObject(elevatorEntry);
	elevator.removeObject(floors[currentFloor]->fromElevator);
	currentRoom->showText("The elevator doors close.");
	elevator.showText("The doors close.");
	state = IDLE;
}


ElevatorButton::ElevatorButton(const string& name_, ElevatorController& controller_, int floor_)
	: MudObject(name_, "The elevator button.")
	, controller(controller_)
	, floor(floor_)
{}


VISITOR_ACCEPT(ElevatorButton)


void ElevatorButton::push(MudPlayer& player) { controller.request(player, floor); }

